package mips.estructurasMemoria;

/**
 *Representa a la memoria principal de la arquitectura mips. La memoria consta
 * de NUM_BLOQUES bloques, cada uno de NUM_PALABRAS_BLOQUE palabras. Conceptualmente,
 * la memoria se divide en 2 segmentos, uno para datos y otro para instrucciones.
 * La memoria de instrucciones se extiende de la dirección MIN_MEMORIA_INSTRUCCIONES a MAX_MEMORIA_INSTRUCCIONES
 * y la memoria de datos se extiende de MIN_MEMORIA_DATOS a MAX_MEMORIA_DATOS.
 * Esta clase provee toda la interfaz pública necesaria para leer de y escribir a memoria,
 * garantizando la sincronización en los accesos a la sección de datos y a la sección de
 * instrucciones.
 */
public class Memoria {
    /**Los 128 bloques de 8 palabras cada uno que conforman la memoria
    principal de la arquitectura mips*/
    private Palabra [] memoria;
    /**Número de bloques de la memoria*/
    public static final int NUM_BLOQUES = 128;
    /**Define el número de palabras que hay en un bloque*/
    public static final int NUM_PALABRAS_BLOQUE = 8;
    /**Límite superior de la memoria para datos. Ningún dato debe
     * tener una dirección mayor que esta.
     */
    public static final int MAX_MEMORIA_DATOS = 1023;
    /**Límite inferior de la memoria para datos. Ningún dato debe
     * tener una dirección menor que esta.
     */
    public static final int MIN_MEMORIA_DATOS = 320;
    /**Límite superior de la memoria para instrucciones. Ninguna instrucción debe
     * tener una dirección mayor que esta.
     */
    public static final int MAX_MEMORIA_INSTRUCCIONES = 319;
    /**Límite inferior de la memoria para instrucciones. Ninguna instrucción debe
     * tener una dirección menor que esta.
     */
    public static final int MIN_MEMORIA_INSTRUCCIONES = 0;

    /**
     *  Seccion de datos esta a partir de bloque 40
     */
    public static final int desplazamientoCacheDatos = 40;
    /**Constructor. Crea una memoria de NUM_BLOQUES bloques*/
    public Memoria(){
        memoria = new Palabra [NUM_BLOQUES*NUM_PALABRAS_BLOQUE];
    }
    /**Carga los hilos en la memoria
     * @param hilos arreglo con las palabras que conforman los hilos. El orden
     * de las palabras representa el orden de las instrucciones de los hilos
     * @throws ArrayIndexOutOfBoundsException si hilos contiene más de MAX_MEMORIA_INSTRUCCIONES + 1
     * palabras, ocurre una excepción.
     * @throws NullPointerException si hilos es nulo.
     */
    public void cargarHilos(Palabra[] hilos) throws ArrayIndexOutOfBoundsException{
        if(hilos.length > MAX_MEMORIA_INSTRUCCIONES){ //40 bloques por 12 palabras = 312 palabras
            throw new ArrayIndexOutOfBoundsException("Superó la cantidad de memoria por instrucciones");
        }
        else{
            for(int i = 0; i < hilos.length; i++){
                this.memoria[i] = hilos[i]; //Esto es una copia de objeto a objeto                           //a nivel de miembros
            }
        }
    }
    /**
     * devuelve el bloque con una determinada etiqueta.
     * Cualquier objeto que desee leer un bloque en memoria de datos, debe hacerlo
     * a través de este método para garantizar la exclusión mutua.
     * Requiere que <code>etiqueta</code> sea menor o igual a NUM_BLOQUES
     * @param etiqueta etiqueta del bloque deseado.
     * @return bloque (arreglo de 8 palabras) al que pertenece la palabra
     * con dirección <code>direccion</code>
     * @throws ArrayIndexOutOfBoundsException Si la etiqueta se sale de la memoria
     * para datos.
    */
    public synchronized Palabra[] leerBloqueDatos(int etiqueta){
        if(etiqueta > MAX_MEMORIA_DATOS/NUM_PALABRAS_BLOQUE || etiqueta < MIN_MEMORIA_DATOS/NUM_PALABRAS_BLOQUE){
            throw new ArrayIndexOutOfBoundsException("Dirección inválida");
        }
        return this.leerBloque(etiqueta);
    }
    /**
     * devuelve el bloque con una determinada etiqueta. Cualquier objeto que desee
     * leer un bloque en memoria de instrucciones, debe hacerlo a través de este
     * método para garantizar la exclusión mutua.
     * Requiere que <code>etiqueta</code> sea menor o igual a NUM_BLOQUES y mayor
     * que cero.
     * @param etiqueta etiqueta del bloque deseado.
     * @return bloque número <code>etiqueta</code>
     * @throws ArrayIndexOutOfBoundsException si la etiqueta se sale de la memoria para instrucciones
    */
    public synchronized Palabra[] leerBloqueInstrucciones(int etiqueta)throws ArrayIndexOutOfBoundsException{
         if(etiqueta > MAX_MEMORIA_INSTRUCCIONES/NUM_PALABRAS_BLOQUE || etiqueta < 0){
            throw new ArrayIndexOutOfBoundsException("Dirección inválida");
        }
        return this.leerBloque(etiqueta);
    }
    /**
     * devuelve el bloque con una determinada etiqueta.
     * Requiere que <code>etiqueta</code> sea menor o igual a NUM_BLOQUES
     * @param etiqueta etiqueta del bloque deseado.
     * @return bloque (arreglo de 8 palabras) al que pertenece la palabra
     * con dirección <code>direccion</code>
     * @throws ArrayIndexOutOfBoundsException La etiqueta debe ser mayor que 0
     * y menor que NUM_BLOQUES.
    */
    private synchronized Palabra[] leerBloque(int etiqueta){
        if(etiqueta >= NUM_BLOQUES || etiqueta < 0){
            throw new ArrayIndexOutOfBoundsException("La dirección "+
                    "está fuera del rango de la memoria");
        }
        Palabra[] bloque = new Palabra[NUM_PALABRAS_BLOQUE];
        for(int i = 0; i < NUM_PALABRAS_BLOQUE; i++){
            bloque[i]  = this.memoria[etiqueta*NUM_PALABRAS_BLOQUE+i];
        }
        return bloque;
    }
    /**Escribe en memoria de datos un determinado bloque.
     * @param bloque el bloque que se desea escribir
     * @param etiqueta etiqueta (número) del bloque que se desea escribir
     * @throws ArrayIndexOutOfBoundsException La etiqueta debe ser mayor que 0
     * y menor que NUM_BLOQUES.
     * @throws NullPointerException <code>bloque</code> no puede ser nulo.
     */
    public synchronized void escribirBloqueDatos(Palabra[] bloque, int etiqueta){
        for(int i = 0; i < NUM_PALABRAS_BLOQUE; i++){
            this.memoria[etiqueta*NUM_PALABRAS_BLOQUE+i] = bloque[i];
        }
    }
    /**Escribe en memoria.
     * Cualquier objeto que desee escribir en memoria de instrucciones, debe
     * hacerlo a través de este método para garantizar la exclusión mutua.
     * Requiere que <code>etiqueta</code> sea menor o igual a NUM_BLOQUES
     * @param bloque el bloque que se desea escribir
     * @param etiqueta etiqueta (número) del bloque que se desea escribir
     */
    public synchronized void escribirBloqueInstrucciones(Palabra[] bloque, int etiqueta){
        this.escribirBloque(bloque, etiqueta);
    }
    /**Escribe en memoria un determinado bloque.
     * Requiere que <code>etiqueta</code> sea menor o igual a NUM_BLOQUES y mayor
     * que cero.
     * @param bloque el bloque que se desea escribir
     * @param etiqueta etiqueta (número) del bloque que se desea escribir
     * @throws ArrayIndexOutOfBoundsException La etiqueta debe ser mayor que 0
     * y menor que NUM_BLOQUES.
     * @throws NullPointerException <code>bloque</code> no puede ser nulo.
     */
    private void escribirBloque(Palabra[] bloque, int etiqueta){
        if(etiqueta > NUM_BLOQUES || etiqueta < 0){
            throw new ArrayIndexOutOfBoundsException("La dirección "+
                    "está fuera del rango de la memoria");
        }
        for(int i = etiqueta*NUM_PALABRAS_BLOQUE; i < etiqueta*NUM_PALABRAS_BLOQUE + NUM_PALABRAS_BLOQUE; i++){
            this.memoria[i] = bloque[i-etiqueta*NUM_PALABRAS_BLOQUE];
        }
    }

  /**
   * Escribe una palabra en memoria en una posición determinada
   * @param dato palabra a escribir en la memoria
   * @param direccion posición de la palabra en la memoria. Aquí,
   * la direeción está dada a nivel de palabra y no de byte.
   */
  public synchronized void escribirPalabra(Palabra dato, int direccion) {
    int etiquetaBloque = direccion / (NUM_BLOQUES * NUM_PALABRAS_BLOQUE);
    int numpalabra = etiquetaBloque % NUM_PALABRAS_BLOQUE;
    this.memoria[etiquetaBloque + numpalabra] = dato;
  }

}// fin public class Memoria
